浅析redux saga中间件及用法|原理篇
30 秒速记
- 核心判断:状态管理的核心是把状态变化约束成可追踪的数据流,中间件在派发边界扩展异步、日志和副作用
- 原理主线:围绕 「一、redux-thunk」、「二、redux-saga 简介」、「三、redux-saga使用案例」 建立输入、状态变化与输出之间的因果关系
- 文章范围:总结了redux-saga中间件的原理、用法及其在处理副作用、异步操作中的优势,帮助前端开发者深入理解redux-saga的工作机制与实际应用场景。
- 边界与代价:集中式状态不是越多越好;可变性、订阅粒度和异步取消决定性能与可维护性
- 工程落地:选型要从状态归属、更新频率、调试需求和团队约束出发,而不是只比较 API 数量
redux-saga 通过 Generator 和声明式 Effect 集中编排 Redux 的异步操作与副作用,同时让业务 action 保持为普通对象。 Saga 通常由 watcher 监听 action,再由 worker 使用 call 执行请求、用 put 派发新 action,最终仍由 reducer 同步更新状态。相比 redux-thunk 直接执行函数型 action,Saga 先产出描述操作的对象,再由中间件解释执行,因此流程更集中,也更方便测试和取消。并发场景要注意选择:takeEvery 会处理每次触发,takeLatest 只保留最近一次任务。
这篇文章不要按 API 清单来背。先用上面的 Mind Map 建立全局结构,再通过交互 DEMO 观察正常路径和边界路径如何改变状态;阅读正文时重点核对每一步的输入、负责执行的参与者、产生的中间状态以及最终可观察结果。遇到版本敏感结论,要把“历史实现”“当前行为”和“工程兼容策略”分开说明;遇到性能或架构取舍,则用实际指标、失败现象和验证手段支撑判断。
版本校准: 本文若分析 ReactDOM.render、旧生命周期或栈调和,应把它视为理解架构演进的历史路径。React 19 已移除 ReactDOM.render,当前客户端入口使用 createRoot;并发渲染也必须区分可中断的渲染阶段与同步提交阶段。迁移前对照 React 19 官方升级指南。
# 一、redux-thunk
# 1.1 redux的副作用处理
redux中的数据流大致是
UI—————>action(plain)—————>reducer——————>state——————>UI

redux是遵循函数式编程的规则,上述的数据流中,action是一个原始js对象(plain object)且reducer是一个纯函数,对于同步且没有副作用的操作,上述的数据流起到可以管理数据,从而控制视图层更新的目的- 如果存在副作用函数,那么我们需要首先处理副作用函数,然后生成原始的js对象。如何处理副作用操作,在
redux中选择在发出action,到reducer处理函数之间使用中间件处理副作用
redux增加中间件处理副作用后的数据流大致如下:
UI——>action(side function)—>middleware—>action(plain)—>reducer—>state—>UI

在有副作用的
action和原始的action之间增加中间件处理,从图中我们也可以看出,中间件的作用就是:
- 转换异步操作,生成原始的action,这样,
reducer函数就能处理相应的action,从而改变state,更新UI
# 1.2 redux-thunk源码
在redux中,thunk是redux作者给出的中间件,实现极为简单,10多行代码
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
这几行代码做的事情也很简单,判别action的类型,如果action是函数,就调用这个函数,调用的步骤为
action(dispatch, getState, extraArgument);
发现实参为
dispatch和getState,因此我们在定义action为thunk函数是,一般形参为dispatch和getState
# 1.3 redux-thunk的缺点
thunk`的缺点也是很明显的,`thunk`仅仅做了执行这个函数,并不在乎函数主体内是什么,也就是说`thunk`使得`redux`可以接受函数作为`action`,但是函数的内部可以多种多样。比如下面是一个获取商品列表的异步操作所对应的`action@前端进阶之旅: 代码已经复制到剪贴板
export default ()=>(dispatch)=>{
fetch('/api/goodList',{ //fecth返回的是一个promise
method: 'get',
dataType: 'json',
}).then(function(json){
var json=JSON.parse(json);
if(json.msg==200){
dispatch({type:'init',data:json.data});
}
},function(error){
console.log(error);
});
};
从这个具有副作用的
action中,我们可以看出,函数内部极为复杂。如果需要为每一个异步操作都如此定义一个action,显然action不易维护
action不易维护的原因
action的形式不统一- 就是异步操作太为分散,分散在了各个
action中
# 二、redux-saga 简介
redux-saga是一个redux中间件,它具有如下特性
- 集中处理
redux副作用问题。 - 被实现为
generator。 - 类
redux-thunk中间件。 watch/worker(监听->执行) 的工作形式
redux-saga的优点
- 集中处理了所有的异步操作,异步接口部分一目了然
action是普通对象,这跟redux同步的action一模一样- 通过
Effect,方便异步接口的测试 - 通过
worker和watcher可以实现非阻塞异步调用,并且同时可以实现非阻塞调用下的事件监听 - 异步操作的流程是可以控制的,可以随时取消相应的异步操作
基本用法
- 使用
createSagaMiddleware方法创建saga的Middleware,然后在创建的redux的store时,使用applyMiddleware函数将创建的saga Middleware实例绑定到store上,最后可以调用saga Middleware的run函数来执行某个或者某些Middleware。 - 在
saga的Middleware中,可以使用takeEvery或者takeLatest等API来监听某个action,当某个action触发后,saga可以使用call发起异步操作,操作完成后使用put函数触发action,同步更新state,从而完成整个State的更新。
# 三、redux-saga使用案例
redux-saga是控制执行的generator,在redux-saga中action是原始的js对象,把所有的异步副作用操作放在了saga函数里面。这样既统一了action的形式,又使得异步操作集中可以被集中处理redux-saga是通过genetator实现的,如果不支持generator需要通过插件babel-polyfill转义。我们接着来实现一个输出hellosaga的例子
创建一个helloSaga.js文件
export function * helloSaga() {
console.log('Hello Sagas!');
}
在redux中使用redux-saga中间件
在
main.js中
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { helloSaga } from './sagas'
const sagaMiddleware=createSagaMiddleware();
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(helloSaga);
//会输出Hello, Sagas!
和调用
redux的其他中间件一样,如果想使用redux-saga中间件,那么只要在applyMiddleware中调用一个createSagaMiddleware的实例。唯一不同的是需要调用run方法使得generator可以开始执行
# 四、redux-saga使用细节
# 4.1 声明式的Effect
在
redux-saga中提供了一系列的api,比如take、put、all、select等API,在redux-saga中将这一系列的api都定义为Effect。这些Effect执行后,当函数resolve时返回一个描述对象,然后redux-saga中间件根据这个描述对象恢复执行generator中的函数
redux-thunk的大体过程
action1(side function)`—>`redux-thunk`监听—>执行相应的有副作用的方法—>`action2(plain object)@前端进阶之旅: 代码已经复制到剪贴板

转化到
action2是一个原始js对象形式的action,然后执行reducer函数就会更新store中的state
redux-saga的大体过程
action1(plain object)——>redux-saga监听—>执行相应的Effect方法——>返回描述对象—>恢复执行异步和副作用函数—>action2(plain object)

对比
redux-thunk我们发现,redux-saga中监听到了原始js对象action,并不会马上执行副作用操作,会先通过Effect方法将其转化成一个描述对象,然后再将描述对象,作为标识,再恢复执行副作用函数
# 4.2 Effect提供的具体方法
下面来介绍几个
Effect中常用的几个方法,从低阶的API,比如take,call(apply),fork,put,select等,以及高阶API,比如takeEvery和takeLatest等
import {take,call,put,select,fork,takeEvery,takeLatest} from 'redux-saga/effects'
# 4.2.1 take
take这个方法,是用来监听action,返回的是监听到的action对象。比如
const loginAction = {
type:'login'
}
在
UI Component中dispatch一个action
dispatch(loginAction)
